package servidor;

import java.io.*;
import java.net.*;
import java.util.regex.*;
import java.util.*;

public final class PedidoHttp implements Runnable
{
	private static final Pattern formatoDoGet = Pattern.compile("^GET (/.*) HTTP/1.[1]$");
    private final File diretorioDoSite;
    private final Socket socket;
    
    public PedidoHttp(Socket socket, File diretorioDoSite)
    {
    	this.socket = socket;
    	this.diretorioDoSite = diretorioDoSite;
    }
    
    // método que pega o caminho do pedido feito pelo browser
    private String leCaminhoDoPedido() throws IOException
    {
        BufferedReader leitor = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
        String primeiraLinha = leitor.readLine();
        
        if (primeiraLinha == null)
        {
        	return null;
        }
	    
	    Matcher combinacao = formatoDoGet.matcher(primeiraLinha);
	    
	    if(combinacao.matches())
	    {
	    	return combinacao.group(1);
	    }
	    else
	    {
	    	return null;
	    }
    }

    private OutputStream enviaCabecalho(int status, String mensagem, long tamanho) throws IOException
    {
    	Date dataDeHoje = new Date();
	    StringBuffer cabecalho = new StringBuffer();
	    OutputStream saida = this.socket.getOutputStream();
	    
	    cabecalho.append("HTTP/1.1 ");
	    cabecalho.append(status).append(' ').append(mensagem).append("\r\n");
	    cabecalho.append("Date: ");
	    cabecalho.append(dataDeHoje).append(("\r\n"));
	    cabecalho.append("Content-Length: ").append(tamanho).append("\r\n\r\n");
	    
	    // escreve os dados do cabeçalho para o socket
	    saida.write(cabecalho.toString().getBytes());
	    // força a escrita dos dados imediatamente
	    saida.flush();
	    return saida;
    }

    private int mandaMensagemDeErro(int status, String mensagem) throws IOException
    {
	    OutputStream saida = enviaCabecalho(status, mensagem, mensagem.length());
	    saida.write((mensagem+"\nerror number: " + status + "\n").getBytes());
	    saida.flush();
	    return status;
    }

    // le o arquivo, byte a byte, do diretório e envia seu conteúdo pelo socket para que o navegador possa ter acesso pela porta
    private long enviaArquivo(File arquivo) throws IOException
    {
    	long tamanhoDoArquivo = arquivo.length();
    	OutputStream saida = enviaCabecalho(200, "OK", tamanhoDoArquivo);
    	InputStream entrada = new FileInputStream(arquivo);
    	
    	try
    	{
    		byte[] buffer = new byte[1024];
    		int ultimoByte = 0;
    		while ((ultimoByte = entrada.read(buffer)) > 0)
            {
    			saida.write(buffer, 0, ultimoByte);
            }
        }
    	finally
        {
            entrada.close();
        }
    	saida.flush();
    	return tamanhoDoArquivo;
    }

    public void run()
    {
	    int status = 200;
	    long tamanhoDoArquivo = 0;
	    String caminhoDoArquivo = null;
	    
	    try
	    {
	    	caminhoDoArquivo = leCaminhoDoPedido();
	    	
            if (caminhoDoArquivo == null)
            {
                status = mandaMensagemDeErro(400, "Bad Request");
            }
            
            if(caminhoDoArquivo.equals("/"))
            {
                caminhoDoArquivo=caminhoDoArquivo+"index.html";
                System.out.println("O arquivo pedido está em: "+this.diretorioDoSite.getAbsolutePath()+caminhoDoArquivo);
                
                File arquivo = new File(this.diretorioDoSite, caminhoDoArquivo);
                
                if (!arquivo.exists())
            	{
            		status = mandaMensagemDeErro(404, "Not Found");
            	}
            	else
                {
            		tamanhoDoArquivo = enviaArquivo(arquivo);
                }
            }
            // tenta achar o arquivo pedido se foi em um diretorio diferente do /
            else
            {
                File arquivo = new File(this.diretorioDoSite, caminhoDoArquivo);
                System.out.println(arquivo.getAbsolutePath());
                
                // testa se o diretorio do site é um prefixo do caminho do arquivo pedido 
                // ou se o arquivo existe e se não é um diretório e pode ser lido
                // se o diretório do site não é um prefixo do caminho do arquivo pedido
                // ou se o arquivo não existe ou é um diretório ou não pode ser lido pelo browser
                // então manda uma mensagem de erro
                // caso contrário apenas testa se o arquivo existe
                // se não existe diz que não foi achado senão manda o arquivo pedido
                if (!arquivo.getAbsolutePath().startsWith(this.diretorioDoSite.getAbsolutePath()) || (arquivo.exists() && (!arquivo.isFile() || !arquivo.canRead())))
                {
                    status = mandaMensagemDeErro(403, "Forbidden");
                }
                else
                {
                	if (!arquivo.exists())
                	{
                		status = mandaMensagemDeErro(404, "Not Found");
                	}
                	else
                    {
                		System.out.println("O arquivo pedido está em: "+this.diretorioDoSite.getAbsolutePath()+caminhoDoArquivo);
                		tamanhoDoArquivo = enviaArquivo(arquivo);
                    }
            	}
            }
        }
        catch (IOException e)
        {
        	System.out.println(e);
        }
        finally
        {
            try
            {
            	this.socket.close();
            }
            catch (IOException e)
            {
                System.out.println(e);
            }
        }
    }
}
